package prolog.io

import java.io.IOException
import java.io.Reader
import java.io.StreamTokenizer
import java.util.ArrayList
import prolog.builtins.Builtins
import prolog.fluents.HashDict
import prolog.terms.Clause
import prolog.terms.Conj
import prolog.terms.Cons
import prolog.terms.Const
import prolog.terms.Fun
import prolog.terms.Int
import prolog.terms.Nonvar
import prolog.terms.Real
import prolog.terms.Term
import prolog.terms.Var



class Lexer(I:Reader) extends StreamTokenizer
{
  input: Reader;
  this.input=I;
  parseNumbers();
  eolIsSignificant(true);
  ordinaryChar('.');
  
    quoteChar('\'');
  quoteChar('\"');
  wordChar('$');
  wordChar('_');
  ordinaryChar('-'); // creates problems with -1 etc.
  ordinaryChar('/');

  slashStarComments(true);
  commentChar('%');
  val dict=new HashDict();
  
  def this(path:String,s:String) ={
    this(IO.url_or_file(path+s)); // stream
  }
  
 
 
  
  def this() ={
    this(IO.input);
  }
  
   def this(String s) ={
    this(IO.string_to_stream(s));
  }
  
  anonymous:String ="_".intern()
  char2string(int c):String = ""+c.asInstanceOf[char]
   inClause: boolean=false;
   def atEOF(): boolean ={
    yes:boolean =(TT_EOF==ttype);
    if(yes)
      try {
        input.close();
      } catch {
        case ioe:IOException => IO.trace("unable to close atEOF");
      }
    return yes;
  }
  
def atEOC():boolean = !inClause
def make_const(s: String):Term= new constToken(s)
def make_fun(s: String):Term= new funToken(s)
def make_int(n: double):Term= new intToken(n.asInstanceOf[int])

 make_real(n: double):Term= new realToken(n)
  
 make_number(nval: double):Term= {
    T: Term;
    if(Math.floor(nval)==nval)
      T=make_int(nval);
    else
      T=make_real(nval);
    return T;
  }
  
  make_var(s: String): Term= {
    val s=s.intern();
    X:Var;
    val occ:long;
    if(s==anonymous) {
      occ=0;
      val X=new Var();
      s=X:asInstanceOf(String);
    } else {
      X=dict.asInstanceOf(Var).get(s);
      if(X==null) {
        occ=1;
        X=new Var();
      } else {
        occ=((Int)dict.get(X)).longValue();
        occ++;
      }
    }
    val I:Int=new Int(occ);
    dict.put(X,I);
    dict.put(s,X);
    return new varToken(X,new Const(s),I);
  }
  
  wordChar(c: char)= {
    wordChars(c,c);
  }
  
  val dict: HashDict;
  
  getWord(quoted: boolean):Term ={
    val T:Term;
    if(quoted&&0==sval.length())
      val T=make_const("");
    else {
      val c:char=sval.charAt(0);
      if(!quoted&&(Character.isUpperCase(c)||'_'==c))
        T=make_var(sval);
      else { // nonvar
        val s:String=sval;
        val nt:int=nextToken();
        pushBack();
        T=('('==nt)?make_fun(s):make_const(s);
      }
    }
    return T;
  }

next():Term= {
    n: Int =nextToken();
    val inClause=true;
    val T:Term;
    n match {
      case TT_WORD =>
        T=getWord(false)
      
      case '\'' =>
        T=getWord(true)
            
      case TT_NUMBER =>
        T=make_number(nval)
            
      case TT_EOF =>
        T=new eofToken()
        inClause=false
            
      case TT_EOL =>
        T=next()
            
      case '-' =>
        if(TT_NUMBER==nextToken()) {
          T=make_number(-nval);
        } else {
          pushBack();
          T=make_const(char2string(n));
        }
        
      case ':' =>
        if('-'==nextToken()) {
          T=new iffToken(":-");
        } 
        else {
          pushBack();
          T=make_const(char2string(n));
        }
      
      
      case '.' =>
        int c=nextToken();
        if(TT_EOL==c||TT_EOF==c) {
          inClause=false;
          // dict.clear(); ///!!!: this looses Var names
          T=new eocToken();
        } 
	   else {
          pushBack();
          T=make_const(char2string(n)); // !!!: sval is gone
        }
            
      case '\"' =>
        T=new stringToken(getWord(true).asInstanceOf(constToken));
      
      case '(' =>
        T=new lparToken()

      case ')' =>
        T=new rparToken()

      case '[' =>
        T=new lbraToken()
      
      case ']' =>
        T=new rbraToken()
      
      case '|' =>
        T=new barToken()
      
      case ',' =>
        T=new commaToken()
      
      default =>
        T=make_const(char2string(n));
    }
    // IO.mes("TOKEN:"+T);
    return T;
  }
}


class varToken( X:var, C:Const, I:Int) extends Fun("varToken",3) {
  
    args[0]=X;
    args[1]=C;
    args[2]=I;
  
}

class intToken( i:int) extends Fun("intToken",new Int(i)) {
  
}

class realToken(i:double) extends Fun("realToken",new Real(i)) {
 
}

class constToken( c:Const) extends Fun("constToken",c) {
  
    args[0]=Builtins.toConstBuiltin(c);
  
  
  def constToken(String s){
    this(new Const(s));
  }
}

class stringToken(c:constToken) extends Fun("stringToken",(c.args[0])) {
  
}

class funToken(s:String) extends Fun("funToken",new Fun(s) {
 
}

class eocToken() extends Fun( "eocToken",new Const("end_of_clause")){
  
    
}

class eofToken() extends Fun("eofToken",Const.anEof) {
  
}

class iffToken(s:String) extends Fun("iffToken",new Const(s)) {
  
}

class Token(s:String extends Const(s)
{
  T
}

class lparToken() extends Token("(") {
  
}

class rparToken() extends Token(")") {
  
}

class lbraToken() extends Token("[") {
  
}

class rbraToken() extends Token("]") {
 
}

class barToken() extends Token("|") {
  
}

class commaToken() extends Token(",") {
 
}

/**
  Simplified Prolog parser:
  Synatax supported:
  a0:-a1,...,an.
  - no operators ( except toplevel :- and ,)
  - use quotes to create special symbol names, i.e.
  compute('+',1,2, Result) and  write(':-'(a,','(b,c)))
*/

 class Parser(I:Reader) extends Lexer(I) {
  
 
  
  /*
    used in prolog2java
  */
  def Parser( p:String, s:String) throws IOException{
    super(p,s);
  }
  
  def Parser( s:String) throws Exception{
    super(s);
  }
  
  /**
    Main Parser interface: reads a clause together
    with variable name information
  */
  def Clause readClause() {
    var t:Clause=null;
    var verbose:boolean=false;
    try {
      t=readClauseOrEOF();
      // IO.mes("GOT Clause:"+t);
    }
   

   catch {case e:ParserException=> t=errorClause(e,"syntax_error",lineno(),verbose);
      try {
        while(!atEOC()&&!atEOF())
          next();
      } catch {case toIgnore:IOException=>
      }
    } catch {case e:IOException=> t=errorClause(e,"io_exception",lineno(),verbose);
    } catch {case e :Exception
      t=errorClause(e,"unexpected_syntax_exception",lineno(),true);
    }
    return t;
  }
  

  def  final boolean isError(Clause C) {
    val H:Term=C.getHead();
    if(H instanceof Fun&&"error".equals(((Fun)H).name())&&H.getArity()==3
        &&!(((Fun)H).args[0].ref() instanceof Var))
      return true;
    return false;
  }
  
    def final Clause errorClause(Exception e,String type,int line,
      boolean verbose) 
	  {
    
    var mes:String=e.getMessage();
    if(null==mes)
      mes="unknown_error";
    val f:Fun=new Fun("error",new Const(type),new Const(mes),new Fun("line",
        new Int(line)));
    val C:Clause=new Clause(f,Const.aTrue);
    if(verbose) {
      IO.errmes(type+" error at line:"+line);
      IO.errmes(C.pprint(),e);
    }
    return C;
  }
  
  
   def  final void showError(Clause C) {
    IO.errmes("*** "+C);
  }
  
  def final Clause toClause(Term T,HashDict dict) {
    val C:Clause=T.toClause(); // adds ...:-true if missing
    C.dict=dict;
    return C;
  }
  
  def Clause readClauseOrEOF() throws IOException {
    
     val dict=new HashDict();
    
    val n:Term=next();
    
    // IO.mes("readClauseOrEOF 0:"+n);
    
    if(n instanceof eofToken)
      return null; // $$toClause(n.token(),dict);
      
    if(n instanceof iffToken) {
      n=next();
      val t:Term=getTerm(n);
      val bs:Term=getConjCont(t);
      Clause C=new Clause(new Const("init"),bs);
      C.dict=dict;
      return C;
    }
    
    val h:term=getTerm(n);
    
    // IO.mes("readClauseOrEOF 1:"+h);
    
    n=next();
    
    // IO.mes("readClauseOrEOF 2:"+n);
    
    if(n instanceof eocToken||n instanceof eofToken)
      return toClause(h,dict);
    
    // IO.mes("readClauseOrEOF 3:"+b);
    
    val C:Clause=null;
    if(n instanceof iffToken) {
      val t:Term=getTerm();
      val bs:Term=getConjCont(t);
      C=new Clause(h,bs);
      C.dict=dict;
    } else if(n instanceof commaToken) {
      val b:Term=getTerm();
      val bs:Term=getConjCont(b);
      C=toClause(new Conj(h,bs),dict);
    } else {
      throw new ParserException("':-' or '.' or ','","bad body element",n);
    }
    return C;
  }
  
  def final Term getConjCont(Term curr) throws IOException {
    
    val n:Term=next();
    val t:Term=null;var t:Term=null
    if(n instanceof eocToken)
      t=curr;
    else if(n instanceof commaToken) {
      val other:Term=getTerm();
      t=new Conj(curr,getConjCont(other));
    }
    if(null==t) {
      throw new ParserException("'.'","bad body element",n);
    }
    return t;
  }
  
  def final Term getTerm(Term n) throws IOException {
    val t:Term=n.token();
    if(n instanceof varToken||n instanceof intToken||n instanceof realToken
        ||n instanceof constToken) {
      // is just OK as it is
    } else if(n instanceof stringToken) {
      t=((Nonvar)((stringToken)n).args[0]).toChars();
      // IO.mes("getTerm:stringToken-->"+t);
      
    } else if(n instanceof lbraToken) {
      t=getList();
    } else if(n instanceof funToken) {
      val f:Fun=(Fun)t;
      f.args=getArgs();
      t=Builtins.toFunBuiltin(f);
    } else
      throw new ParserException("var,int,real,constant,'[' or functor",
          "bad term",n);
    return t;
  }
  
  def Term getTerm() throws IOException {
     val n:Term=next();
    return getTerm(n);
  }
  
  def final Term[] getArgs() throws IOException {
    val n:Term=next();
    if(!(n instanceof lparToken))
      throw new ParserException("'('","in getArgs",n);
    val v:ArrayList=new ArrayList();
    val t:Term=getTerm();
    v.add(t);
    for(;;) {
      n=next();
      if(n instanceof rparToken) {
        var l:Int=v.size();
        val args[]:Term=new Term[l];
        // v.copyInto(args);
        Object[] as=v.toArray();
        for(int i=0;i<l;i++) {
          args[i]=(Term)as[i];
        }
        return args;
      } else if(n instanceof commaToken) {
        t=getTerm();
        v.add(t);
      } else {
        throw new ParserException("',' or ')'","bad arg",n);
      }
    }
  }
  
  def final Term getList() throws IOException {
    val n:Term=next();
    if(n instanceof rbraToken)
      return Const.aNil;
    val t:Term=getTerm(n);
    return getListCont(t);
  }
  
  def final Term getListCont(Term curr) throws IOException {
    // IO.trace("curr: "+curr);
    val n:Term=next();
    val t:Term=null;
    if(n instanceof rbraToken)
     val  t=new Cons(curr,Const.aNil);
    else if(n instanceof barToken) {
      val t=new Cons(curr,getTerm());
      n=next();
      if(!(n instanceof rbraToken)) {
        throw new ParserException("']'","bad list end after '|'",n);
      }
    } else if(n instanceof commaToken) {
      val other:Term=getTerm();
      t=new Cons(curr,getListCont(other));
    }
    if(t==null)
      throw new ParserException("| or ]","bad list continuation",n);
    return t;
  }
  
  def  final String patchEOFString(String s) {
    if(!(s.lastIndexOf(".")>=s.length()-2))
      s=s+".";
    return s;
  }
  
  def static Clause clsFromString(String s) {
    if(null==s)
      return null;
    s=patchEOFString(s);
    val t:Clause=null;
    try {
      Parser p;
     val p=new Parser(s);
     val  t=p.readClause();
    } catch(Exception e) { // nothing expected to catch
      IO.errmes("unexpected parsing error",e);
    }
    if(t.dict==null)
      t.ground=false;
    else
      t.ground=t.dict.isEmpty();
    return t;
  }
  
}

class ParserException extends IOException {
  public ParserException(String e,String f,Term n){
    super("expected: "+e+", found: "+f+"'"+n+"'");
  }
}
